package net.pulseproject.router.config.parser;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import net.pulseproject.commons.util.SetterInvocationException;
import net.pulseproject.commons.util.StringUtil;
import net.pulseproject.router.config.ModuleFilter;
import net.pulseproject.router.config.dependency.DependencySorter;
import net.pulseproject.router.config.instantiator.ModuleInstantiator;
import net.pulseproject.router.config.parser.ConfigParserErrors.ParseError;
import net.pulseproject.router.modules.Module;
import net.pulseproject.router.modules.misc.RoutingTable;
import net.pulseproject.router.modules.useable.MidiTransmitterModule;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;

public class ConfigParserImpl implements ConfigParser {
	
	private static final Log LOG = LogFactory.getLog(ConfigParserImpl.class);
	
	private final LineParser lineParser;

	private final DependencySorter dependencySorter;
	
	private final ModuleInstantiator moduleInstantiator;
	
	
	@Inject
	public ConfigParserImpl(final LineParser lineParser, final DependencySorter dependencySorter,
			final ModuleInstantiator moduleInstantiator) {
		LOG.debug("new ConfigParserImpl(..)");
		this.lineParser = lineParser;
		this.dependencySorter = dependencySorter;
		this.moduleInstantiator = moduleInstantiator;
	}

	@Override
	public final RoutingTable parse(final String rawConfigText, ConfigParserErrors errors) {
		if(rawConfigText == null) { throw new IllegalArgumentException("rawConfigText == null"); }
		if(errors == null) { throw new IllegalArgumentException("errors == null"); }
		LOG.debug("parse(rawConfigText, errors)");
		
		final Collection<LineConfig> unsortedLineConfigs = this.parseLines(rawConfigText, errors);
		final Collection<LineConfig> sortedLineConfigs = this.dependencySorter.sortById(unsortedLineConfigs);
		final Collection<Module> allModules = this.instantiateModules(sortedLineConfigs, errors);
		if(errors.isError() == true) {
			return null;
		}
		
		final ModuleFilter moduleFilter = new ModuleFilter(); // TODO make explicit dependency?
		final Collection<MidiTransmitterModule> transmitterModules =
			moduleFilter.filterByType(allModules, MidiTransmitterModule.class);
		
		if(errors.isError() == false && transmitterModules.isEmpty() == true) {
			errors.addError(new ParseError("You need to configure at least one transmitter module!"));
			return null;
		}
		
		final RoutingTable table = new RoutingTable(transmitterModules);
		LOG.info("parse(rawConfigText) COMPLETE ================================");
		return table;
	}
	
	private Collection<LineConfig> parseLines(final String rawConfigText, ConfigParserErrors errors) {
		final String[] rawConfigLines = StringUtil.splitAndTrimElements(rawConfigText, "\n");
		LOG.trace("parseLines(rawConfigText) ... rawConfigLines.size=" + rawConfigLines.length);
		
		final LineFilter filter = new LineFilter();
//		final Collection<String> filteredConfigLines = filter.filterValidLines(rawConfigLines);
		final Collection<LineConfig> unsortedLineConfigs = new HashSet<LineConfig>(rawConfigLines.length);
		
		int lineNumber = 0;
		for (final String rawConfigLine : rawConfigLines) {
			lineNumber++;
			
			if(filter.validateLine(rawConfigLine) == false) {
				LOG.trace("Skipping line " + lineNumber + ": " + rawConfigLine);
				continue;
			}
			
			final LineConfig lineConfig = this.lineParser.parseLine(rawConfigLine, lineNumber, errors);
			if(lineConfig != null) { // otherwise error has occured
				unsortedLineConfigs.add(lineConfig);
			}
		}
		
		return unsortedLineConfigs;
	}
	
	private Collection<Module> instantiateModules(final Collection<LineConfig> sortedLineConfigs, ConfigParserErrors errors) {
		LOG.trace("instantiateModules(sortedLineConfigs)");
		
		final Map<String, Module> availableModules = new HashMap<String, Module>();
		for (final LineConfig lineConfig : sortedLineConfigs) {
			try {
				final Module module = this.moduleInstantiator.instantiate(lineConfig, availableModules, sortedLineConfigs);
				LOG.trace("  ... instantiated module: " + module);
				availableModules.put(module.getId(), module);
			} catch(SetterInvocationException e) {
				LOG.warn("Could not instantiate module by: " + lineConfig + "!", e);
				errors.addError(new ParseError("Could not instantiate "+lineConfig.getType()+" " +
						"with ID '" + lineConfig.getId() + "': Setting property '" + e.getPropertyName() + "' failed: " + e.getCause().getMessage()));
				return null;
			}
		}
		return availableModules.values();
	}

}
